<template>
    <div :style="{ '--percent': percent, '--height': height }" class="container"
        :class="{ [theme]: true, 'is-moving': isMoving }" @pointerdown="down" @click="click">
        <div ref="bar" class="bar">
            <div class="valid"></div>
            <div class="invalid"></div>
        </div>
        <div ref="slipper" class="slipper" :title="`${value}`">
            <div class="show-value">{{ showText }}</div>
        </div>
    </div>
</template>

<script lang="ts" setup>
import { RangeBarOptions } from '@/types/ui';
import { computed, ref } from 'vue';
const { height = '20px', min = 0, defaultValue, max = 100, step = 1, theme } = defineProps<RangeBarOptions>();
const value = ref(defaultValue ?? max);
const percent = computed(() => `${(value.value - min) / (max - min) * 100}%`);
const showText = computed(() => `${value.value % 1 === 0 ? value.value : value.value.toFixed(1)}`)
const slipper = ref<HTMLDivElement>();
const bar = ref<HTMLDivElement>();
const isMoving = ref(false);
const emit = defineEmits(['change']);
function click(e: PointerEvent) {
    const progress = e.offsetX * (max - min) / bar.value.clientWidth
    value.value = Math.min(max, Math.max(Math.round(progress / step) * step + min));
    emit('change', { value: value.value });
}
function down(e: PointerEvent) {
    const target = e.target as HTMLElement;
    if (!slipper.value.contains(target)) return;
    isMoving.value = true;
    function move(event: PointerEvent) {
        if (!isMoving.value) return;
        const rect = bar.value.getBoundingClientRect();
        const offsetX = event.clientX - rect.left;
        const safeOffsetX = Math.min(rect.width, Math.max(0, offsetX));
        const progress = safeOffsetX * (max - min) / rect.width;
        value.value = Math.min(max, Math.max(Math.round(progress / step) * step + min, min));
        emit('change', { value: value.value });
    }
    function up() {
        if (!isMoving.value) return;
        isMoving.value = false;
        document.removeEventListener('pointermove', move);
        document.removeEventListener('pointerup', up);
    }
    document.addEventListener('pointermove', move);
    document.addEventListener('pointerup', up);
}
</script>

<style scoped>
.container {
    height: var(--height);
    position: relative;
    cursor: pointer;
}

.container.is-moving * {
    pointer-events: none;
}

.bar {
    height: 100%;
    width: 100%;
    display: flex;
    border-radius: calc(var(--height) / 2);
    position: absolute;
    overflow: hidden;
}

.bar,
.bar * {
    pointer-events: none;
}

.bar .valid {
    width: var(--percent);
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
}

.bar .invalid {
    flex: 1;
    height: 100%;
    background-color: rgb(128, 128, 128, 0.8);
}

.slipper {
    --slipper-size: calc(var(--height));
    height: var(--slipper-size);
    width: var(--slipper-size);
    left: min(max(calc(var(--percent) - var(--slipper-size) / 2), 0px), calc(100% - var(--slipper-size)));
    position: absolute;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    border: calc(var(--height) / 8) solid rgb(240, 248, 255);
    box-shadow: -1px -1px 3px rgb(255, 255, 255, 0.7), 1px 1px 4px rgb(0, 0, 0, 0.4);
}

.slipper .show-value {
    font-size: calc(3 * var(--height) /8);
    transition: 0.1s;
}

.ruby .bar {
    background-image: linear-gradient(crimson, maroon);
}

.ruby .slipper {
    background-image: linear-gradient(rgb(224, 17, 95, 0.6), rgb(255, 0, 0, 0.6));
}

.sea .bar {
    background-image: linear-gradient(aqua, navy);
}

.sea .slipper {
    background-image: linear-gradient(rgba(17, 55, 224, 0.6), rgb(0, 0, 255, 0.6));
}

.green .bar {
    background-image: linear-gradient(aquamarine, darkslategrey);
}

.green .slipper {
    background-image: linear-gradient(rgba(127, 255, 212, 0.6), rgba(47, 79, 79, 0.6));
}
</style>